/**
 * Copyright (c) Software Institute of PKU
 * All rights reserved. 
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 */
package edu.pku.sei.modeler.figures;

import static java.lang.Math.abs;
import static java.lang.Math.max;

import java.util.Iterator;

import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.FlowLayout;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.LineBorder;
import org.eclipse.draw2d.Panel;
import org.eclipse.draw2d.ToolbarLayout;
import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.graphics.Color;

import edu.pku.sei.modeler.uml.ResourceFactory;
import edu.pku.sei.modeler.uml.util.ConstrainedToolbarLayout;

/**
 * @generated
 * 
 */
public class LifelineFigure extends CommonFigure {
	private static final int MIN_HEIGHT = 50;

	private static final Dimension DEFAULT_SIZE = new Dimension(100, 320);

	private static final int MIN_WIDTH = 50;

	// Vertical margin above and below the lifeline label(s)
	private static final int LABEL_V_MARGIN = 4;

	// The height of the lifeline label box. This value is calculated
	// based on font size on first access -- see #validate()
	public static int HEAD_HEIGHT = 0;

	// The area (to the left and right) of the lifeline that is sensible to the
	// mouse
	private static final int LIFELINE_HIT_WIDTH = 6;

	public static final int VERTICAL_POSITION = 40;
	
	private static final int STOP_SIZE = 20;

	// Defines the visual appearance of lifelines
	private static int[] LIFELINE_DASH_STYLE = new int[] { 5, 5 };

	private Label textLabel;

	private Label refLabel;

	private Panel lifelineHead;

	private Panel refPanel;
	
	private Color lineColor = ColorConstants.black;

	public LifelineFigure() {
		// setLayoutManager(new BorderLayout());
		setLayoutManager(new XYLayout());

		// Centering text within the lifeline head is not too easy to
		// do using only GEF classes. GMF contains a ConstrainedToolbarLayout
		// which would do the job, but it would add a heavy dependency for
		// very little gain. Therefore we replicate the class here.
		ConstrainedToolbarLayout headLayout = new ConstrainedToolbarLayout(ToolbarLayout.VERTICAL);
		headLayout.setMinorAlignment(ToolbarLayout.ALIGN_CENTER);
		headLayout.setStretchMajorAxis(false);
		headLayout.setMajorAlignment(ToolbarLayout.ALIGN_CENTER);

		lifelineHead = new Panel();
		lifelineHead.setBorder(new LineBorder(1));
		lifelineHead.setLayoutManager(headLayout);

		add(lifelineHead);
		// add(lifelineHead, BorderLayout.TOP);
		textLabel = new Label("lifeline");
		textLabel.setFont(ResourceFactory.getHeadFont());
		lifelineHead.add(textLabel);

		FlowLayout refLayout = new FlowLayout(FlowLayout.HORIZONTAL);
		refLayout.setMajorAlignment(FlowLayout.ALIGN_CENTER);
		refLayout.setMinorAlignment(FlowLayout.ALIGN_CENTER);
		refPanel = new Panel();
		refPanel.setLayoutManager(refLayout);
		// refPanel.add(new Label("ref "));

		refLabel = new Label();
		refLabel.setFont(ResourceFactory.getHeadFont());
		refPanel.add(refLabel);

		refPanel.setOpaque(false);

		add(refPanel);
	}

	public IFigure getContentPane() {
		return this;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void setBounds(Rectangle rect) {
		// System.out.println(this.getPreferredBounds(new Rectangle(0, 0, 0,
		// 0)));
		for (Iterator iterator = this.getChildren().iterator(); iterator.hasNext();) {
			IFigure figure = (IFigure) iterator.next();
			if (figure instanceof StopFigure) {
				StopFigure stopFigure = (StopFigure) figure;
				stopFigure.setCanMove(true);
				int x = rect.width / 2 - stopFigure.getBounds().width / 2;
				int y = rect.height - stopFigure.getBounds().height;
				
				figure.setBounds(new Rectangle(x, y, stopFigure.getBounds().width, stopFigure.getBounds().height));
				stopFigure.setCanMove(false);
			}
			else if (figure instanceof StateInvariantFigure) {
				StateInvariantFigure stateInvariantFigure = (StateInvariantFigure) figure;
				stateInvariantFigure.setCanMove(true);
				int x = rect.width / 2 - stateInvariantFigure.getBounds().width / 2;
				//int y = rect.height - stateInvariantFigure.getBounds().height;
				
				figure.setBounds(new Rectangle(x, stateInvariantFigure.getBounds().y, stateInvariantFigure.getBounds().width, stateInvariantFigure.getBounds().height));
				stateInvariantFigure.setCanMove(false);
			}
			else if (figure instanceof ExecutionOccurrenceFigure) {
				ExecutionOccurrenceFigure executionOccurrenceFigure = (ExecutionOccurrenceFigure) figure;
				executionOccurrenceFigure.setCanMove(true);
				int x = rect.width / 2 - executionOccurrenceFigure.getBounds().width / 2;
				figure.setBounds(new Rectangle(x, executionOccurrenceFigure.getBounds().y, executionOccurrenceFigure.getBounds().width, executionOccurrenceFigure.getBounds().height));
				executionOccurrenceFigure.setCanMove(false);
			}
			
		}
		
		int width = rect.width > MIN_WIDTH ? rect.width : MIN_WIDTH;
		int height = HEAD_HEIGHT;
		this.lifelineHead.setBounds(new Rectangle(0, 0, width, height));
		super.setBounds(rect);


	}

	public Rectangle getPreferredBounds(Rectangle suggestedBounds) {
		// TODO The top y should depend on any present local attribute figures
		// Lifelines should also not overlap
		int y = (suggestedBounds.y < 70) ? 40 : suggestedBounds.y;
		int width, height;
		if (suggestedBounds.width == 0) {
			width = DEFAULT_SIZE.width;
			height = getParent().getBounds().height - y - 15;
		} else {
			width = max(suggestedBounds.width, MIN_WIDTH);
			height = max(suggestedBounds.height, MIN_HEIGHT);
		}
		return new Rectangle(suggestedBounds.x, y, width, height);
	}

	@Override
	protected void paintFigure(Graphics graphics) {
		graphics.pushState();
		textLabel.setFont(this.getFont());
		refLabel.setFont(this.getFont());
		// textLabel.setForegroundColor(this.getFontColor());
		// refLabel.setForegroundColor(this.getFontColor());
		lifelineHead.setForegroundColor(lineColor);
		lifelineHead.setFont(this.getFont());
		Rectangle rect = getBounds().getCopy();
		int middle = rect.width / 2;
		graphics.setForegroundColor(lineColor);
		graphics.setLineStyle(Graphics.LINE_CUSTOM);
		graphics.setLineDash(LIFELINE_DASH_STYLE);
		
		// YangYuanzu: do NOT draw the dash line from the bottom of the head 
		// to the bottom of the lifeline, 
		// but left margin for the Destruction Event Figure. 
		graphics.drawLine(rect.x + middle, rect.y + HEAD_HEIGHT, 
				rect.x + middle, rect.bottom() - STOP_SIZE / 2);

		// setting the linedash attribute has a sideeffect on any connection
		// figures drawn later. The original value is 'null', which we can't
		// restore due to the implementation of setLineDash method. Fortunately
		// we can _almost_ restore it this way:
		// FIXME Talk with people about this problem
		graphics.setLineStyle(Graphics.LINE_SOLID);
		graphics.setLineDash(new int[] {});
		graphics.popState();
	}

	public void changeLineColor(Color color) {
		lineColor = color;
	}
	
	@Override
	public void validate() {
		if (isValid())
			return;

		super.validate();

		// By now child labels are laid out by GEF, and we can query it
		// to determine the actual height of the lifeline "box" in such
		// as way as to neatly fit the text label(s).
		// Only do this once though.
		if (HEAD_HEIGHT == 0)
			HEAD_HEIGHT = 2 * textLabel.getBounds().height + 2 * LABEL_V_MARGIN;

		if (lifelineHead.getPreferredSize().height != HEAD_HEIGHT) {
			lifelineHead.setPreferredSize(new Dimension(-1, HEAD_HEIGHT));
		}
	}

	@Override
	public boolean containsPoint(int x, int y) {
		Rectangle rect = getBounds();

		// Check if point is inside head!
		// TODO This task can be delegated to the root panel
		// but in that case, coordinates have to be converted to local
		if (x > rect.x && x < rect.x + rect.width && y > rect.y && y < rect.y + HEAD_HEIGHT - 1)
			return true;

		return (x > (rect.x + rect.width / 2 - LIFELINE_HIT_WIDTH) && x < (rect.x + rect.width / 2 + LIFELINE_HIT_WIDTH) && y > rect.y && y < rect.y
				+ rect.height);
	}

	public static final int TARGET_NAME = 1;

	public static final int TARGET_REF = 2;

	/**
	 * Determines which label (the lifeline name or the lifeline decomposition
	 * name) owns the given point.
	 * 
	 * @param location
	 * @return TARGET_NAME (default) or TARGET_REF
	 */
	public int findEditTargetAt(Point location) {
		if (location == null)
			return TARGET_NAME;

		Rectangle bounds = getRefBounds();
		return (getRefText() != null && bounds.contains(location)) ? TARGET_REF : TARGET_NAME;
	}

	@Override
	protected boolean useLocalCoordinates() {
		return true;
	}

	// --- ILabeledFigure ---

	public Rectangle getLabelBounds() {
		Rectangle rect = textLabel.getClientArea().getCopy();
		textLabel.translateToAbsolute(rect);
		return rect;
	}

	public void setLabel(String labelText) {
		textLabel.setText(labelText);
		textLabel.setFont(ResourceFactory.getHeadFont());
	}

	// extensions due to extra decomposition label

	public String getRefText() {
		return refLabel.getText();
	}

	public void setRefText(final String newText) {
		if (newText == null && lifelineHead.getChildren().contains(refPanel))
			lifelineHead.remove(refPanel);
		else if (newText != null && !lifelineHead.getChildren().contains(refPanel))
			lifelineHead.add(refPanel);

		refLabel.setText(newText);
		invalidate();
	}

	public Rectangle getRefBounds() {
		Rectangle rect = refLabel.getClientArea().getCopy();
		refLabel.translateToAbsolute(rect);
		return rect;
	}

	// --- Used by Coverable analysis ---

	/**
	 * Calculates the horizontal distance from this lifeline to the center of
	 * the given rectangle.
	 * 
	 * @param fragmentBounds
	 *            The bounds whose distance is tested. The coordinates must be
	 *            given in absolute coordinates.
	 * @returns the (always positive) horizontal distance.
	 */
	public int getDistanceTo(Rectangle fragmentBounds) {
		Rectangle lifelineBounds = Rectangle.SINGLETON;
		lifelineBounds.setBounds(getBounds());
		translateToAbsolute(lifelineBounds);
		return abs(fragmentBounds.x - lifelineBounds.x + (fragmentBounds.width - lifelineBounds.width) / 2);
	}

	/**
	 * Tests whether the given rectangle covers this lifeline.
	 * 
	 * @param fragmentBounds
	 *            The bounds whose distance is tested. The coordinates must be
	 *            given either in absolute coordinates or in interaction
	 *            coordinates.
	 * @param local
	 *            if true, the coordinates are given in interaction-local
	 *            coordinates. if false, they are absolute coordinates. TODO
	 *            Always use absolute coordinates
	 */
	public boolean isCovered(final Rectangle fragmentBounds, final boolean local) {
		final Rectangle lifelineBounds = Rectangle.SINGLETON;
		lifelineBounds.setBounds(getBounds());
		if (!local) {
			// translateToParent(lifelineBounds);
			translateToAbsolute(lifelineBounds);
		}

		final int lifelineX = lifelineBounds.x + lifelineBounds.width / 2;
		return (lifelineX >= fragmentBounds.x && lifelineX < fragmentBounds.right());
	}

	public Label getTextLabel() {
		return textLabel;
	}

}
